package org.calzz.jajb.converter;

import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.calzz.jajb.JsonDeserializer;
import org.calzz.jajb.JsonSerializer;
import org.calzz.jajb.io.IWriter;
import org.calzz.jajb.utils.ReflectionUtils;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;

/**
 * The default converter for Java <code>Map</code> type. Currently doesn't
 * support unmarhalling for generic types.
 * 
 * @author Marek Brodziak <marek.brodziak@gmail.com>
 * @author Piotr Pejas <piotr.pejas@gmail.com>
 * @since Feb 15, 2008
 */
public class MapConverter implements IConverter {
	private Log log = LogFactory.getLog(MapConverter.class);
	private Map<Class<?>, Class<?>> _typeCache = new HashMap<Class<?>, Class<?>>();

	/*
	 * (non-Javadoc)
	 * @see org.jamjam.jajb.converter.IConverter#canConvert(java.lang.Class)
	 */
	public boolean canConvert(Class<?> clazz) {
		return ReflectionUtils.isInstanceOf(clazz, Map.class);
	}

	/*
	 * (non-Javadoc)
	 * @see org.jamjam.jajb.converter.IConverter#marshal(java.lang.Object,
	 *      java.lang.String, org.jamjam.jajb.io.IWriter,
	 *      org.jamjam.jajb.JsonSerializer)
	 */
	public void marshal(Object obj, String name, IWriter writer, JsonSerializer serializer) {
		writer.startObject(name);
		Map<?, ?> map = (Map<?, ?>) obj;
		for (Object o : map.keySet()) {
			serializer.process(map.get(o), o.toString(), writer);
		}
		writer.endObject();
	}

	/*
	 * (non-Javadoc)
	 * @see org.jamjam.jajb.converter.IConverter#unmarshal(java.lang.Object,
	 *      java.lang.Class, org.jamjam.jajb.JsonDeserializer)
	 */
	@SuppressWarnings("unchecked")
	public <T> Object unmarshal(Object env, Class<T> clazz, JsonDeserializer deserializer, MethodParameter methodParam) {
		Map<Object, Object> out = null;
		clazz = resolveImplementation(clazz);
		try {
			out = (Map<Object, Object>) clazz.newInstance();
		} catch (Exception e) {
			log.error("Cannot instantiate type: " + clazz.getName());
			throw new RuntimeException(e);
		}

		Class valueClass = GenericCollectionTypeResolver.getMapValueParameterType(methodParam);
		Class keyClass = GenericCollectionTypeResolver.getMapKeyParameterType(methodParam);

		Map<String, Object> in = (Map<String, Object>) env;
		for(String key : in.keySet()) {
			out.put(
					deserializer.process(key, keyClass, null),
					deserializer.process(in.get(key), valueClass, null)
			);
		}
		return out;
	}

	@SuppressWarnings("unchecked")
	private Class resolveImplementation(Class<?> clazz) {
		Class<?> implementationType = _typeCache.get(clazz);

		if (implementationType == null) {
			if (clazz.isInterface()) {
				if (Map.class.equals(clazz)) {
					implementationType = HashMap.class;
				} else if (SortedMap.class.equals(clazz)) {
					implementationType = TreeMap.class;
				} else {
					//cannot find the implementation
					log.error("Cannot find proper implementation of " + clazz.getName());
					throw new RuntimeException("Cannot find proper implementation of " + clazz.getName());
				}
			} else {
				implementationType = clazz;
			}

			if (implementationType != null) {
				_typeCache.put(clazz, implementationType);
			}
		}

		return implementationType;
	}

}
